﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using RQX.Common.Web.Tools;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace RQX.Common.Web.Web.DynamicApi
{
    public class ApiServiceConvention : IApplicationModelConvention
    {
        private readonly IServiceCollection services;
        public ApiServiceConvention(IServiceCollection services)
        {
            this.services = services;
        }

        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                controller.Actions
                    .Where(k => ReflectionHelper.GetSingleAttributeOrDefault<IgnoreApiServiceAttribute>(k.Controller.GetType(), false) != null
                    || ReflectionHelper.GetSingleAttributeOrDefault<IgnoreApiServiceAttribute>(k.ActionMethod, false) != null
                    || ReflectionHelper.GetSingleAttributeOrDefault<IgnoreApiServiceAttribute>(k.ActionMethod.DeclaringType, false) != null)
                    .ToList()
                    .ForEach(item => controller.Actions.Remove(item));
                var type = controller.ControllerType.AsType();
                var attribute = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<ApiServiceAttribute>(type.GetTypeInfo());
                if (typeof(IApiService).IsAssignableFrom(type) && attribute != null)
                {
                    controller.ControllerName = controller.ControllerName.RemoveEndStr("Service");
                    //设置区域
                    ConfigureArea(controller, attribute);
                    ConfigureApiExplorer(controller);
                    ConfigureSelector(controller, attribute);
                    ConfigureParameters(controller);
                }
            }
        }

        private void ConfigureParameters(ControllerModel controller)
        {
            foreach (var action in controller.Actions)
            {
                foreach (var para in action.Parameters)
                {
                    if (para.BindingInfo != null)
                    {
                        continue;
                    }

                    //if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(para.ParameterInfo.ParameterType))
                    //{
                    if (CanUseFormBodyBinding(action, para))
                    {
                        para.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
                    }
                    //}
                }
            }
        }

        private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
        {
            //if (AppConsts.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
            //{
            //    return false;
            //}

            foreach (var selector in action.Selectors)
            {
                if (selector.ActionConstraints == null)
                {
                    continue;
                }

                foreach (var actionConstraint in selector.ActionConstraints)
                {
                    if (!(actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint))
                    {
                        continue;
                    }

                    if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "TRACE", "HEAD")))//"DELETE",
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private void ConfigureSelector(string areaName, string controllerName, ActionModel action)
        {
            RemoveEmptySelectors(action.Selectors);

            if (null != ReflectionHelper.GetSingleAttributeOrDefault<IgnoreApiServiceAttribute>(action.ActionMethod, false))
            {
                return;
            }

            string routeStr;
            if (areaName.IsNullOrEmpty())
            {
                routeStr = $"api/{controllerName}/{action.ActionName}";
            }
            else
            {
                routeStr = $"api/{areaName}/{controllerName}/{action.ActionName}";
            }

            if (!action.Selectors.Any())
            {
                var appServiceSelectorModel = new SelectorModel
                {
                    AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(routeStr))
                };
                appServiceSelectorModel.ActionConstraints.Add(SetHttpMethod(action.ActionName));
                action.Selectors.Add(appServiceSelectorModel);
            }
            else
            {
                foreach (var selector in action.Selectors)
                {
                    selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(routeStr));
                }
            }
        }

        private HttpMethodActionConstraint SetHttpMethod(string actionName)
        {
            actionName = actionName.ToLower();
            if (actionName.Contains("remove") || actionName.Contains("delete"))
            {
                return new HttpMethodActionConstraint(new[] { "DELETE" });
            }
            else if (actionName.Contains("update") || actionName.Contains("edit"))
            {
                return new HttpMethodActionConstraint(new[] { "PUT" });
            }
            else if (actionName.Contains("add") || actionName.Contains("insert"))
            {
                return new HttpMethodActionConstraint(new[] { "POST" });
            }
            else
            {
                return new HttpMethodActionConstraint(new[] { "GET" });
            }
        }


        private static void RemoveEmptySelectors(IList<SelectorModel> selectors)
        {
            selectors
                .Where(IsEmptySelector)
                .ToList()
                .ForEach(s => selectors.Remove(s));
        }

        private static bool IsEmptySelector(SelectorModel selector)
        {
            return selector.AttributeRouteModel == null && selector.ActionConstraints.IsNullOrEmpty();
        }

        private void ConfigureSelector(ControllerModel controller, ApiServiceAttribute attribute)
        {
            var areaName = attribute.AreaName;

            foreach (var action in controller.Actions)
            {
                ConfigureSelector(areaName, controller.ControllerName, action);
            }
        }

        private void ConfigureApiExplorer(ControllerModel controller)
        {
            if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
            {
                controller.ApiExplorer.GroupName = controller.ControllerName;
            }

            if (controller.ApiExplorer.IsVisible == null)
            {
                controller.ApiExplorer.IsVisible = true;
            }

            foreach (var action in controller.Actions)
            {
                if (action.ApiExplorer.IsVisible == null)
                {
                    action.ApiExplorer.IsVisible = true;
                }
            }
        }

        private void ConfigureArea(ControllerModel controller, ApiServiceAttribute attribute)
        {
            if (attribute.AreaName.IsNullOrEmpty())
            {
                controller.RouteValues["area"] = "NoAreaName";
            }
            else
            {
                controller.RouteValues["area"] = attribute.AreaName;
            }
        }
    }
}
